<?php

namespace app\extra\wx;

use Exception;

/**
 * 微信WxSdk
 */
class WxSdk
{
    private $app_id;
    private $app_secret;

    public function __construct($app_id, $app_secret)
    {
        $this->app_id     = $app_id;
        $this->app_secret = $app_secret;
        touch("js_api_ticket.json");
        touch("access_token.json");
    }

    /**
     * 对URL签名
     * @param $url string 需要签名的URL 带参数完整的URL
     * @return array
     * @throws Exception
     */
    public function getSignPackage(string $url)
    {
        $jsApiTicket = $this->getJsApiTicket();
        $timestamp   = time();
        $nonceStr    = $this->getNonceStr();
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string    = "jsapi_ticket=" . $jsApiTicket . "&noncestr=" . $nonceStr . "&timestamp=" . $timestamp . "&url=" . $url;
        $signature = sha1($string);
        return [
            "appId"     => $this->app_id,
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => $signature,
            "rawString" => $string
        ];
    }

    /**
     * 获取JsApiTicket
     * @return mixed
     * @throws Exception
     */
    public function getJsApiTicket()
    {
        $get = function () {
            $accessToken = $this->getAccessToken();
            $url         = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
            $result      = json_decode($this->getData($url));

            // 异常处理
            if (isset($result->errcode) && $result->errcode != 0) throw new Exception($result->errmsg, 500);

            $data['expire_time'] = time() + 7000;
            $data['ticket']      = $result->ticket;
            $fp                  = fopen("js_api_ticket.json", "w");
            fwrite($fp, json_encode($data));
            fclose($fp);
            return $result->ticket;
        };

        // jsApiTicket 应该全局存储与更新，以下代码以写入到文件中做示例
        $data = json_decode(file_get_contents("js_api_ticket.json"));
        if (isset($data->expire_time)) {
            if ($data->expire_time < time()) {
                return $get();
            } else {
                return $data->ticket;
            }
        } else {
            return $get();
        }
    }

    /**
     * 获取AccessToken
     * @return mixed
     * @throws Exception
     */
    public function getAccessToken()
    {
        $get = function () {
            $url    = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->app_id&secret=$this->app_secret";
            $result = json_decode($this->getData($url));

            // 异常处理
            if (isset($result->errcode)) throw new Exception($result->errmsg, 500);

            $data['expire_time']  = time() + 7000;
            $data['access_token'] = $result->access_token;
            $fp                   = fopen("access_token.json", "w");
            fwrite($fp, json_encode($data));
            fclose($fp);
            return $result->access_token;
        };
        // access_token 应该全局存储与更新，以下代码以写入到文件中做示例
        $data = json_decode(file_get_contents("access_token.json"));
        if (isset($data->expire_time)) {
            if ($data->expire_time < time()) {
                return $get();
            } else {
                return $data->access_token;
            }
        } else {
            return $get();
        }
    }

    /**
     * 获取随机字符串
     * @param int $length
     * @return string
     */
    private function getNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str   = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * GET方式 请求数据
     * @param $url
     * @return bool|string
     */
    private function getData($url)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 500);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_URL, $url);
        $res = curl_exec($curl);
        curl_close($curl);
        return $res;
    }

    /**
     * post https请求
     * @param $url
     * @param string $data 数据
     * @return bool|string
     */
    private function postData($url, string $data)
    {
        // 初始化curl
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($curl, CURLOPT_HEADER, FALSE);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($curl, CURLOPT_POST, TRUE);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        $res = curl_exec($curl);
        curl_close($curl);
        return $res;
    }

    /**
     * 小程序二维码，适用于使用数量极多的场景
     * @param string $page 扫码打开的页面
     * @param string $scene 传入参数 例：邀请人id 123 用户id 456 传入的参数为 123&456
     * @param int $width 宽度 默认480
     * @param bool $is_hyaline 是否透明 默认透明
     * @return mixed
     * @throws Exception
     */
    public function getWxQrcode(string $page, string $scene, $width = 480, $is_hyaline = false)
    {
        $access_token = $this->getAccessToken();
        $url          = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=$access_token";
        $is_hyaline && $is_hyaline = true;
        $data = json_encode([
            'page'       => $page,
            'scene'      => $scene,
            'width'      => $width,
            'is_hyaline' => $is_hyaline,
        ]);
        return $this->postData($url, $data);
    }

    /**
     * @param $touser string 接收者（用户）的 openid
     * @param $template_id string 所需下发的订阅模板id
     * @param $page string 点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转。
     * @param $data Object 模板内容，格式形如 { "key1": { "value": any }, "key2": { "value": any } }
     * @param string $miniprogram_state string 跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
     * @throws Exception
     */
    public function subscribeSend(string $touser, string $template_id, string $page, $data, $miniprogram_state = 'formal')
    {
        $access_token = $this->getAccessToken();
        $url          = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=$access_token";
        $data_        = json_encode([
            'touser'            => $touser,
            'template_id'       => $template_id,
            'page'              => $page,
            'miniprogram_state' => $miniprogram_state,
            'data'              => $data
        ],JSON_UNESCAPED_UNICODE);
        return $this->postData($url, $data_);
    }
}